/**
    @file pixelnode.cpp
    @brief Game room node
*/

#include "pixel/pixelnode.hpp"
#include "pixel/pixelobj.hpp"

pm3::PIXELNODE *pm3::lua::g_active_node = NULL;
int pm3::PIXELNODE::m_num_entities = 0;
int pm3::PIXELNODE::m_num_nodes = 0;

pm3::PIXELNODE::PIXELNODE() {
    m_object = NULL;
    m_node = NULL;
}

pm3::PIXELNODE::~PIXELNODE() {
    m_object = NULL;
    size_t i;

    for (i=0; i<m_rigid_bodies.size(); i++)
        if (m_rigid_bodies [i])
            delete m_rigid_bodies [i];

    for (i=0; i<m_collision_shapes.size(); i++)
        if (m_collision_shapes [i])
            delete m_collision_shapes [i];

    m_rigid_bodies.clear();
    m_collision_shapes.clear();

    m_nodes.clear();
    m_entities.clear();
}

int pm3::PIXELNODE::load_scene( const std::string &r_path, const std::string &r_group ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    Ogre::PCZSceneManager *scene_mgr = NULL;

    scene_mgr = pm3::INTERFACE::getSingleton().get_scenemgr();
    if (scene_mgr && m_node)
        m_scene.parseDotScene( r_path, r_group, scene_mgr, m_node );

    if (m_node && log)
        log->report( "Pixel", "I: Loaded scene \"%s\" to pixelnode root \"%s\"\n", r_path.c_str(), m_node->getName().c_str() );

    return 0;
}

int pm3::PIXELNODE::add_model( const std::string &r_path ) {
    Ogre::SceneManager *scene_mgr = NULL;
    Ogre::Entity *entity = NULL;

    // Create another entity
    scene_mgr = pm3::INTERFACE::getSingleton().get_scenemgr();
    if (scene_mgr) {
        entity = scene_mgr->createEntity( r_path );

        m_entities.push_back( entity );

        return m_entities.size() - 1;
    }

    return -1;
}

int pm3::PIXELNODE::add_node( int i_parent ) {
    LOGGER *log = LOGGER::getSingletonPtr();

    Ogre::SceneNode *node = NULL;
    std::string str = "PixelNode" + pm3::util::to_str( m_num_nodes ) + 
                        "SubNode" + pm3::util::to_str( m_nodes.size() );

    // Create another node
    if (m_node && i_parent == -1) {
        node = m_node->createChildSceneNode( str );
        m_nodes.push_back( node );

        if (log)
            log->report( "Pixel", "I: Added node %d to pixelnode root\n", m_nodes.size() - 1 );

        return m_nodes.size() - 1;
    } else {
        if (i_parent >= 0 && i_parent < (int) m_nodes.size() && m_nodes [i_parent]) {
            node = m_nodes [i_parent]->createChildSceneNode( str );
            m_nodes.push_back( node );

            if (log)
                log->report( "Pixel", "I: Added node %d to pixelnode subnode %d\n", m_nodes.size() - 1, i_parent );

            return m_nodes.size() - 1;
        }
    }

    if (log)
        log->report( "Pixel", "E: The pixelnode doesn't have subnode %d\n", i_parent );

    return -1;
}

int pm3::PIXELNODE::attach_model( int i_model, int i_node ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    Ogre::Entity *entity = NULL;

    if (i_model >= 0 && i_model < (int) m_entities.size() && m_entities [i_model] &&
        i_node >= 0 && i_node < (int) m_nodes.size() && m_nodes [i_node]) {
        // Attach the entity to the node
        m_nodes [i_node]->attachObject( m_entities [i_model] );

        if (log)
            log->report( "Pixel", "I: Attached model %d to node %d\n", i_model, i_node );

        return 0;
    }

    if (log)
        log->report( "Pixel", "E: There is either no node %d or no model %d\n", i_node, i_model );

    return -1;
}

int pm3::PIXELNODE::scale_node( int i_node, Ogre::Vector3 v_scale ) {
    // Attempt the scaling
    if (i_node >= 0 && i_node < (int) m_nodes.size() && m_nodes [i_node]) {
        m_nodes [i_node]->scale( v_scale );

        return 0;
    } else if (i_node < 0 && m_node) {
        m_node->scale( v_scale );

        return 0;
    }

    return -1;
}

int pm3::PIXELNODE::translate_node( int i_node, Ogre::Vector3 v_offset ) {
    // Attempt the translation
    if (i_node >= 0 && i_node < (int) m_nodes.size() && m_nodes [i_node]) {
        m_nodes [i_node]->translate( v_offset );

        return 0;
    } else if (i_node < 0 && m_node) {
        m_node->translate( v_offset );

        return 0;
    }

    return -1;
}

int pm3::PIXELNODE::rotate_node( int i_node, Ogre::Vector3 v_angles ) {
    // Attempt the rotation
    if (i_node >= 0 && i_node < (int) m_nodes.size() && m_nodes [i_node]) {
        m_nodes [i_node]->pitch( Ogre::Radian( Ogre::Degree( v_angles.x ).valueRadians() ) );
        m_nodes [i_node]->yaw( Ogre::Radian( Ogre::Degree( v_angles.y ).valueRadians() ) );
        m_nodes [i_node]->roll( Ogre::Radian( Ogre::Degree( v_angles.z ).valueRadians() ) );

        return 0;
    } else if (i_node < 0 && m_node) {
        m_node->pitch( Ogre::Radian( Ogre::Degree( v_angles.x ).valueRadians() ) );
        m_node->yaw( Ogre::Radian( Ogre::Degree( v_angles.y ).valueRadians() ) );
        m_node->roll( Ogre::Radian( Ogre::Degree( v_angles.z ).valueRadians() ) );

        return 0;
    }

    return -1;
}

void pm3::PIXELNODE::unload() {
    pm3::PHYSICS *physics = pm3::PHYSICS::getSingletonPtr();
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    LOGGER *log = LOGGER::getSingletonPtr();
    size_t i;

    if (!intf || !intf->get_scenemgr())
        return;

    if (!physics || !physics->get_physics_world()) {
        if (log)
            log->report( "Physics", "E: Physics must be initialized before the initialization of any nodes\n" );
        return;
    }

    if (m_node) {
        // Make sure we're not rendering the node
        m_node->setVisible( false );
        // Remove and destroy all children
        m_node->removeAndDestroyAllChildren();
        // We may render the node again
        m_node->setVisible( true );

        // Destroy all nodes
        for (i=0; i<m_nodes.size(); i++) {
            if ( m_nodes [i] )
                intf->get_scenemgr()->destroySceneNode( m_nodes [i]->getName() );
        }
        // And clear the list
        m_nodes.clear();

        // Destroy all entities
        for (i=0; i<m_entities.size(); i++) {
            if ( m_entities [i] )
                intf->get_scenemgr()->destroyEntity( m_entities [i]->getName() );
        }
        // And clear the list
        m_entities.clear();
    }

    // Destroy all rigid bodies
    for (i=0; i<m_rigid_bodies.size(); i++) {
        if (m_rigid_bodies [i]) {
            physics->get_physics_world()->removeRigidBody( m_rigid_bodies [i] );
            delete m_rigid_bodies [i];
        }
    }
    // And clear the list
    m_rigid_bodies.clear();

    // Clear the list of collision shapes
    m_collision_shapes.clear();

    //! \todo Check if there are any memory leaks or double frees
}

void pm3::PIXELNODE::init( PIXELOBJ *p_obj, Ogre::Vector3 v_pos ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    Ogre::SceneNode *world = NULL;

    //! \todo Provide name with grid position suffix
    std::string str = "PixelNode" + pm3::util::pos_suffix( v_pos );

    if (log)
        log->report( "Pixel", "I: Initializing node \"%s\"\n", str.c_str() );

    // Unload any previously loaded nodes
    unload();

    // Set the object
    m_object = p_obj;

    // Create the room node
    world = pm3::INTERFACE::getSingleton().get_world_node();
    if (world && !m_node) {
        m_node = (Ogre::PCZSceneNode *) world->createChildSceneNode( str );
        m_num_nodes++;

        m_node->translate( v_pos );
    }/* else {
        if (log)
            log->report( "Pixel", "E: Failed to create a room node \"%s\"\n", str.c_str() );
    }*/
}

void pm3::PIXELNODE::add_body( Ogre::Entity *p_entity, bool b_dynamic ) {
    pm3::PHYSICS *physics = pm3::PHYSICS::getSingletonPtr();
    LOGGER *log = LOGGER::getSingletonPtr();
    btRigidBody *body = NULL;
    btCollisionShape *shape = NULL;
    btDefaultMotionState *groundstate = NULL;
    BtOgre::RigidBodyState *state = NULL;
    Ogre::PCZSceneNode *node = NULL;
    btScalar friction = 0.5, mass = 5, margin = 1.0f;
    std::string str, collShape;

    if (!physics || !physics->get_physics_world()) {
        if (log)
            log->report( "Physics", "E: Physics must be initialized before the post-setup of any nodes\n" );
        return;
    }

    if (p_entity) {
        // Find the node this entity is attached to
        node = (Ogre::PCZSceneNode *) p_entity->getParentSceneNode();

        if (!node) {
            if (log)
                log->report( "Physics", "E: Pixelnode has entity \"%s\" that's not connected to any nodes\n", p_entity->getName().c_str() );
            return;
        }

        if (log)
            log->report( "Physics", "I: Creating rigid body for entity \"%s\" with parent trail \"%s\"\n", 
                        p_entity->getName().c_str(), pm3::util::trace_parents( node ).c_str() );

        // Get mass
        str = m_scene.getProperty( p_entity->getName(), "mass" );
        if (!str.empty())
            mass = pm3::util::str_to_float( str );
        // Get friction
        str = m_scene.getProperty( p_entity->getName(), "friction" );
        if (!str.empty())
            friction = pm3::util::str_to_float( str );
        // Get collision shape type
        collShape = m_scene.getProperty( p_entity->getName(), "collShape" );

        // Convert Ogre::Entity -> Bullet collision shape
        BtOgre::StaticMeshToShapeConverter converter( p_entity );

        if (!b_dynamic) {
            // Trimesh is the default for static objects
            if (collShape.empty() || collShape == "trimesh") {
                shape = converter.createTrimesh();

                // The following is to work around Bullet internal edges issue

                btTriangleInfoMap* triangleInfoMap = new btTriangleInfoMap();

//                triangleInfoMap->m_equalVertexThreshold = 0.01f;
//                triangleInfoMap->m_edgeDistanceThreshold = 0.0f;//0.0001f;
//                triangleInfoMap->m_planarEpsilon = 0.001f;
//                triangleInfoMap->m_convexEpsilon = 0.001f;
//                triangleInfoMap->m_zeroAreaThreshold = 0.001f;

                // btGenerateInternalEdgeInfo fills in the btTriangleInfoMap and stores it as a user 
                // pointer of trimeshShape (trimeshShape->setUserPointer(triangleInfoMap))
                btGenerateInternalEdgeInfo( ( btBvhTriangleMeshShape * ) shape, triangleInfoMap );

            } else if (collShape == "sphere")
                shape = converter.createSphere();
            else if (collShape == "box") {
                shape = converter.createBox();
//                shape->setMargin( 0.1f );
            } else if (collShape == "cylinder")
                shape = converter.createCylinder();
            else if (collShape == "convex")
                shape = converter.createConvex();

            // Scale the shape accordingly to the parent node
            shape->setLocalScaling( BtOgre::Convert::toBullet( node->_getDerivedScale() ) );

            // Groundstate
            groundstate = new btDefaultMotionState( btTransform( BtOgre::Convert::toBullet( node->_getDerivedOrientation() ), 
                                                                 BtOgre::Convert::toBullet( node->_getDerivedPosition() ) ) );

            btRigidBody::btRigidBodyConstructionInfo c_info( 0, groundstate, shape );
            c_info.m_friction = friction;

            // Create the body
            body = new btRigidBody( c_info );

            // Use custom material callback when it's a trimesh
            if (collShape.empty() || collShape == "trimesh")
                body->setCollisionFlags( body->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK );

        } else {
            // Convex is default for dynamic objects
            if (collShape.empty() || collShape == "convex") {
                shape = converter.createConvex();
                margin = 1.0f - shape->getMargin();
            } else if (collShape == "sphere")
                shape = converter.createSphere();
            else if (collShape == "box")
                shape = converter.createBox();
            else if (collShape == "cylinder")
                shape = converter.createCylinder();

            // Calculate inertia
	        btVector3 inertia;
	        shape->calculateLocalInertia( mass, inertia );

            // Scale the shape accordingly to the parent node
            // Also, in order to reduce the hovering, subtract collision margin
            shape->setLocalScaling( BtOgre::Convert::toBullet( node->_getDerivedScale() ) * margin );

            // Create a state
	        state = new BtOgre::RigidBodyState( node );

            btRigidBody::btRigidBodyConstructionInfo c_info( mass, state, shape, inertia );
            c_info.m_friction = friction;

	        // Create the Body.
	        body = new btRigidBody( c_info );
        }

        // Add the body for physics action
        physics->get_physics_world()->addRigidBody( body );
        m_rigid_bodies.push_back( body );
        m_collision_shapes.push_back( shape );
    } else {
        if (!m_node) {
            if (log)
                log->report( "Init", "E: Pixelnode init must be called before post_setup\n" );
            return;
        }

        //! \todo Make sure that all debug messages contain Ogre object names and not pointers or somesuch
        if (log)
            log->report( "Physics", "E: Pixelnode \"%s\" has an entity with NULL pointer\n", m_node->getName().c_str() );        
    }
}

void pm3::PIXELNODE::post_setup() {
    LOGGER *log = LOGGER::getSingletonPtr();
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();

    if (!intf || !intf->get_scenemgr())
        return;

    if (!m_node) {
        if (log)
            log->report( "Init", "E: Pixelnode init must be called before post_setup\n" );
        return;
    }

    // Update callback contents
    if (m_object)
        m_object->update_script_snippets();

    //! \todo Make sure that all debug messages contain Ogre object names and not pointers or somesuch
    if (log)
        log->report( "Physics", "I: Creating rigid bodies for all static objects in pixelnode \"%s\"\n", m_node->getName().c_str() );

    // Add rigid bodies to all .scene static entities
    for (size_t i=0; i<m_scene.staticObjects.size(); i++)
        add_body( intf->get_scenemgr()->getEntity( m_scene.staticObjects [i] ) );

    // Add rigid bodies to all entities
    for (size_t i=0; i<m_entities.size(); i++) {
        add_body( m_entities [i] );
        if (m_entities [i])
            m_entities [i]->setCastShadows( false );
    }

    //! \todo Make sure that all debug messages contain Ogre object names and not pointers or somesuch
    if (log)
        log->report( "Physics", "I: Creating rigid bodies for all dynamic objects in pixelnode \"%s\"\n", m_node->getName().c_str() );

    // Add rigid bodies to all .scene dynamic entities
    for (size_t i=0; i<m_scene.dynamicObjects.size(); i++)
        add_body( intf->get_scenemgr()->getEntity( m_scene.dynamicObjects [i] ), true );

    // Update node bounding volume
    if (m_node)
        m_node->_update( true, true );
}

bool pm3::PIXELNODE::is_initialized() const {
    return (m_node != NULL && m_object != NULL);
}

